package com.vhall.component.service.ctrl.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.vhall.component.dao.ctrl.AnchorExtendsMapper;
import com.vhall.component.entity.ctrl.AnchorExtendsEntity;
import com.vhall.component.entity.ctrl.AnchorExtendsEntityDTO;
import com.vhall.component.entity.ctrl.vo.ConnectJudgeRspVO;
import com.vhall.component.entity.ctrl.vo.ConnectNumSetReqVo;
import com.vhall.component.entity.room.dto.RoomsEntityDTO;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.plugin.common.constant.RedisKey;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.plugin.common.utils.RedisLocker;
import com.vhall.component.plugin.core.enums.SendMessageTypeEnum;
import com.vhall.component.plugin.core.service.PaasService;
import com.vhall.component.service.ctrl.CtrlService;
import com.vhall.component.service.ctrl.constant.CtrlConst;
import com.vhall.component.service.room.RoomInnerService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 并发控制服务类
 *
 * @author xuyongzheng
 * @since 2021/5/17 上午11:18
 **/
@Slf4j
@Service
public class CtrlServiceImpl implements CtrlService {

    private static final String REMAIN_COUNT_KEY = "remain_count";

    private static final String RATIO_KEY = "ratio";


    @Value("${vhall.connect.appMax}")
    private Integer appMax;

    @Value("${vhall.connect.accountMax}")
    private Integer accountMax;

    @Autowired
    private AnchorExtendsMapper anchorExtendsMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private PaasService paasService;

    @Autowired
    private RoomInnerService roomInnerService;

    @Override
    public ConnectJudgeRspVO judgment(Integer accountId, String roomId, Integer ilId,
                                      String channelId, String thirdId,
                                      String nifyChannel) {

        if (StringUtils.isEmpty(thirdId)) {
            throw new BusinessException(BizErrorCode.BIZ_CONCURRENT_JUDGE_FAILED);
        }

        // 如果有thirdId则需要有, paas通知的频道编号nifyChannel
        if (StringUtils.isNotEmpty(thirdId)) {
            if (StringUtils.isEmpty(nifyChannel)) {
                throw new BusinessException(BizErrorCode.BIZ_CONCURRENT_JUDGE_FAILED);
            }
        }

        ConnectJudgeRspVO result = new ConnectJudgeRspVO();
        log.info("并发控制-开始-参数-房间所属人id={},ilId={},进入者id={}", accountId, ilId, thirdId);

        result.setOverload(false);
        //是否设置并发限制
        Integer maxConnectCount = -1;
        result.setMaxConnectCount(maxConnectCount);
        //  anchor_extends  查询 用户 是否 设置 并发 控制 ，如果用户没有设置并发控制，直接返回
        AnchorExtendsEntityDTO info = findByAccountId(accountId);
        //未设置并发限制可以通过, 启用 最大值
        Integer settingNum = null;
        if (null == info) {
            settingNum = accountMax;
        } else {
            // DB中设置的最大并发限制数
            settingNum = info.getConnectNum();
        }


        //获取被广播提醒的用户数量 需在连接数计算之前
        Integer notifyCount = getNotifyAccountNum(ilId);
        log.info("并发控制-开始-参数-房间所属人id={},ilId={},进入者id={},被广播提醒的用户数量={}", accountId, ilId, thirdId, notifyCount);
        //应用剩余连接数
        Map<String, Integer> appData = appRemainCount(ilId, notifyCount);
        Integer appRemainCount = appData.get(REMAIN_COUNT_KEY);
        Integer appRatio = appData.get(RATIO_KEY);
        log.info("并发控制-开始-参数-房间所属人id={},ilId={},进入者id={},应用剩余连接数={}，当前连接占比={}", accountId, ilId, thirdId, JSON.toJSONString(appRemainCount), appRatio);

        result.setAppRemain(appRemainCount);
        //获取账户剩余连接数
        Map<String, Integer> accountData = accountRemainCount(accountId, appRemainCount, ilId, settingNum, notifyCount);
        log.info("connect_ctrl,账户计算结果: accountId={},{}", accountId, JSON.toJSONString(accountData));
        Integer accountRemainCount = accountData.get(REMAIN_COUNT_KEY);
        Integer accountRatio = accountData.get(RATIO_KEY);
        log.info("并发控制-开始-参数-房间所属人id={},ilId={},进入者id={},账户剩余连接数{}，当前连接占比={}", accountId, ilId, thirdId, JSON.toJSONString(accountRemainCount), accountRatio);

        result.setAccountRemain(accountRemainCount);


        //保留10秒的广播用户可以通过
        boolean inTime = false;
        if (StringUtils.isNotEmpty(thirdId)) {
            inTime = inNotifyTime(ilId, thirdId);
            log.info("并发控制-开始-参数-房间所属人id={},ilId={},进入者id={},是否还在广播有效期内={}", accountId, ilId, thirdId, inTime);
        }
        Integer roomConnectCount = null;

        //账户连接数 或 app连接数到达90%时 请求paas房间连接数进行更新
        //if (!inTime && (accountRatio >= CtrlConst.CONNECT_RATIO)) {

        if (!inTime && (appRatio >= CtrlConst.CONNECT_RATIO || accountRatio >= CtrlConst.CONNECT_RATIO)) {
            accountRemainCount = adjustConnectCount(accountRemainCount, ilId, channelId, thirdId);
            log.info("并发控制-开始-参数-房间所属人id={},ilId={},进入者id={},adjustConnectCount方法改变账户连接数的结局={}", accountId, ilId, thirdId, accountRemainCount);
            accountRemainCount = accountRemainCount > 0 ? accountRemainCount : 0;

            //账户连接数耗尽 推入队列等待
            if (accountRemainCount == 0 && StringUtils.isNotEmpty(thirdId)) {
                log.info("并发控制-开始-参数-房间所属人id={},ilId={},进入者id={}，账户连接数耗尽，推入队列等待。", accountId, ilId, thirdId);
                redisTemplate.opsForValue().set(RedisKey.CONNECT_COUNT_OF_ACCOUNT_BY_ILID + ilId, accountRemainCount);

                boolean noticeLock = RedisLocker.tryLock(RedisKey.CTRL_NOTICE_LIMITED_LOCK + ilId, "1", 60L * 1000L);
                if (!noticeLock) {
                    //如果尝试加锁不成功，则通过pass向客户端发送消息【客户端将调用接口进行排队】
                    JSONObject body = new JSONObject();
                    body.put("type", SendMessageTypeEnum.CONNECT_LIMITED.getType());
                    paasService.sendMessage(channelId, body.toJSONString());
                }
                result.setNifyChannel(nifyChannel);
                result.setAppId(paasService.getAppId());
                result.setOverload(true);
                return result;
            }
        } else if (StringUtils.isNotEmpty(thirdId)) {
            //应用连接数 不到90%数据时自增
            // redisTemplate.opsForValue().increment(RedisKey.CONNECT_COUNT_BY_APP);
            //账户连接数 不到90% 时房间连接数自增
            Long roomConnectCountCache = redisTemplate.opsForValue().increment(RedisKey.CONNECT_COUNT_BY_ROOM + ilId);
            roomConnectCount = Integer.valueOf(String.valueOf(roomConnectCountCache));
        }

        log.info("connect_ctrl,房间连接数roomConnectCount=>{}, 账户剩余数=>{}", roomConnectCount, accountRemainCount);

        //账户剩余连接数 房间共享
        Integer remainCount = accountRemainCount;
        if (StringUtils.isNotEmpty(thirdId) && !inTime) {
            remainCount = accountRemainCount - 1;
        }
        String k = RedisKey.CONNECT_COUNT_OF_ACCOUNT_BY_ILID + ilId;
        redisTemplate.opsForValue().set(k, remainCount);
        log.info("connect_ctrl,账户剩余连接数房间共享,roomConnectCount=>{}, accountRemainCount=>{}, key=>{}, value=>{}", roomConnectCount, accountRemainCount, k, accountRemainCount - 1);

        //房间并发连接数
        if (null == roomConnectCount) {
            roomConnectCount = (Integer) redisTemplate.opsForValue().get(RedisKey.CONNECT_COUNT_BY_ROOM + ilId);
            roomConnectCount = null == roomConnectCount ? 0 : roomConnectCount;
        }

        log.info("connect_ctrl,房间剩余连接数,il_id=>{}, remainCount=>{}, thirdId=>{}", ilId, remainCount, thirdId);

        //房间当前可达最大连接总数
        maxConnectCount = roomConnectCount + accountRemainCount;
        //广播用户不受限制
        if (inTime) {
            maxConnectCount = -1;
        }
        result.setMaxConnectCount(maxConnectCount);
        return result;
    }


    @Override
    public AnchorExtendsEntityDTO createOrUpdate(ConnectNumSetReqVo vo) {
        AnchorExtendsEntityDTO dto = new AnchorExtendsEntityDTO();
        AnchorExtendsEntity entity;

        QueryWrapper<AnchorExtendsEntity> where = new QueryWrapper<>();
        where.eq("account_id", vo.getAccountId());
        where.isNull("deleted_at");
        where.last("limit 1");

        entity = anchorExtendsMapper.selectOne(where);
        if (null != entity) {
            entity.setConnectNum(vo.getConnectNum());
            entity.setUpdatedAt(LocalDateTime.now());
            anchorExtendsMapper.updateById(entity);
            BeanUtils.copyProperties(entity, dto);
            return dto;
        }
        entity = new AnchorExtendsEntity();
        entity.setConnectNum(vo.getConnectNum());
        entity.setAccountId(vo.getAccountId());
        entity.setCreatedAt(LocalDateTime.now());
        entity.setUpdatedAt(LocalDateTime.now());
        anchorExtendsMapper.insert(entity);
        BeanUtils.copyProperties(entity, dto);
        return dto;
    }

    @Override
    public AnchorExtendsEntityDTO findByAccountId(Integer accountId) {
        AnchorExtendsEntityDTO dto = new AnchorExtendsEntityDTO();
        QueryWrapper<AnchorExtendsEntity> where = new QueryWrapper<>();
        where.eq("account_id", accountId);
        where.isNull("deleted_at");
        where.last("limit 1");
        AnchorExtendsEntity entity = anchorExtendsMapper.selectOne(where);
        if (null == entity) {
            return null;
        }
        BeanUtils.copyProperties(entity, dto);
        return dto;
    }

    /**
     * 用户加入排队队列
     *
     * @param accountId
     * @param ilId
     * @return
     */
    @Override
    public Boolean queueAdd(Integer accountId, Integer ilId) {
        try {
            String key = RedisKey.QUEUE_PERF_CTL_ILID + ilId;

            redisTemplate.opsForList().rightPush(key, accountId);

            redisTemplate.opsForSet().add(RedisKey.QUEUE_CTRL, key);

        } catch (Exception ex) {
            log.error("并发限制入队列失败:", ex);
        }


        return true;
    }

    /**
     * 获取房间下广播通知用户的数量
     *
     * @param ilId 房间编号
     * @return
     */
    public Integer getNotifyAccountNum(Integer ilId) {
        Long maxTime = System.currentTimeMillis();
        Long minTime = System.currentTimeMillis() - CtrlConst.NOTIFY_ACCOUNT_EXIST_TIME * 1000;
        String key = RedisKey.NOTIFY_ACCOUNT_LIST_ILID + ilId;
        Long count = redisTemplate.opsForZSet().count(key, minTime.doubleValue(), maxTime.doubleValue());
        return (count == null) ? 0 : count.intValue();
    }

    /**
     * 应用剩余连接数
     *
     * @return array
     */
    public Map<String, Integer> appRemainCount(Integer ilId, Integer notifyCount) {
        Integer appMaxCount = appMax;
        //应用总连接数
        //Integer cacheNum = (Integer) redisTemplate.opsForValue().get(RedisKey.CONNECT_COUNT_BY_APP);
        Integer cacheNum = getAppTotalcount();
        Integer appConnectCount = cacheNum == null ? 0 : cacheNum;
        //应用剩余连接数
        int remainCount = appMaxCount - appConnectCount - notifyCount;
        log.info("connect_ctrl,应用剩余连接数: 应用允许最大连接appMaxCount={},应用连接数appConnectCount={},被通知用户数notifyCount={},剩余数量={}", appMaxCount, appConnectCount, notifyCount, remainCount);
        //计算应用连接数使用率
        BigDecimal bd = new BigDecimal(appConnectCount)
                .divide(BigDecimal.valueOf(appMaxCount), 2, RoundingMode.HALF_UP)
                .multiply(BigDecimal.valueOf(100L));

        Map<String, Integer> result = new HashMap<>(4);
        result.put(REMAIN_COUNT_KEY, remainCount);
        result.put(RATIO_KEY, bd.intValue());
        return result;
    }

    /**
     * 获取 app级别的总连接数  实际上 app的总连接数 是 每个账号的总连接数 相加
     *
     * @return
     */
    public Integer getAppTotalcount() {
        Integer total = 0;
        Set<Integer> accountIds = redisTemplate.opsForSet().members(RedisKey.LIVING_ROOMS_OF_APPS);
        if (accountIds == null || accountIds.size() == 0) {
            return total;
        }
        for (Integer accountId : accountIds) {
            Set<Integer> roomIds = redisTemplate.opsForSet().members(RedisKey.LIVING_ROOMS_OF_ACCOUNT + accountId);
            if (roomIds == null || roomIds.size() == 0) {
                continue;
            }
            log.info("connect_ctrl,账户下开播的房间,ids={} ", roomIds);
            for (Integer id : roomIds) {
                String k = RedisKey.CONNECT_COUNT_BY_ROOM + id;
                Integer v = (Integer) redisTemplate.opsForValue().get(k);
                int c = v == null ? 0 : v;
                total = total + c;
            }
        }
        return total;

    }

    /**
     * 账户剩余连接数
     *
     * @param accountId      账户id
     * @param appRemainCount app剩余连接数
     * @param notifyCount    被广播提醒的用户数量
     * @return array
     */
    public Map<String, Integer> accountRemainCount(Integer accountId, Integer appRemainCount,
                                                   Integer ilId, Integer settingNum, Integer notifyCount) {
        //剩余连接数 = 设置的账户连接数 - 当前账户连接数

        settingNum = settingNum > 0 ? settingNum : appMax;
        //账户当前连接数
        Integer connectCount = accountConnectCount(accountId, ilId);
        log.info("connect_ctrl,当前账户连接数: accountId={},connectCount={}", accountId, connectCount);

        Integer remainCount = settingNum - connectCount;
        remainCount = remainCount > 0 ? remainCount : 0;


        //更新剩余连接数
        remainCount = remainCount > notifyCount ? remainCount - notifyCount : 0;
        log.info("connect_ctrl,当前账户剩余连接数: accountId={},settingNum={},connectCount={},appRemainCount={}", accountId, settingNum, remainCount, appRemainCount);

        //目前 app级别 控制可能会有小问题  调整中 目前暂时针对 账号级别 控制并发连接数
        if (appRemainCount != null) {
            //剩余连接数 : 应用剩余连接数 和 账户剩余连接 取少的那个值
            remainCount = appRemainCount > remainCount ? remainCount : appRemainCount;

            log.info("connect_ctrl,当前账户剩余连接数: accountId={},settingNum={},connectCount={},appRemainCount={}", accountId, settingNum, remainCount, appRemainCount);
        }


        //计算账户连接数使用率

        BigDecimal divide = new BigDecimal((settingNum - remainCount)).divide(new BigDecimal(settingNum), 2, RoundingMode.HALF_UP);
        BigDecimal ratio = divide.multiply(new BigDecimal(100));

        log.info("connect_ctrl,当前账户连接占比: accountId={},settingNum={},connectCount={},ratio={}", accountId, settingNum, remainCount, ratio);
        Map<String, Integer> result = new HashMap<>(4);
        result.put(REMAIN_COUNT_KEY, remainCount);
        result.put(RATIO_KEY, ratio.intValue());
        return result;
    }

    /**
     * 账户下当前连接数
     *
     * @param accountId
     * @return int
     */
    public Integer accountConnectCount(Integer accountId, Integer ilIdxx) {
        Integer total = 0;

        Set<Integer> roomIds = redisTemplate.opsForSet().members(RedisKey.LIVING_ROOMS_OF_ACCOUNT + accountId);
        if (roomIds == null || roomIds.size() == 0) {
            return total;
        }

        log.info("connect_ctrl,账户下开播的房间,ids={} ", roomIds);
        for (Integer id : roomIds) {
            String k = RedisKey.CONNECT_COUNT_BY_ROOM + id;
            Integer v = (Integer) redisTemplate.opsForValue().get(k);
            int c = v == null ? 0 : v;
            total = total + c;
        }
        return total;
    }

    /**
     * 用户是否在广播通知时间窗口内
     *
     * @param ilId    //房间ID
     * @param thirdId //用户ID
     * @return bool
     */
    public boolean inNotifyTime(Integer ilId, String thirdId) {
        //用户被广播时间
        String key = RedisKey.NOTIFY_ACCOUNT_LIST_ILID + ilId;

        Double value = redisTemplate.opsForZSet().score(key, thirdId);
        value = value == null ? Double.valueOf("0") : value;
        // 毫秒
        Long accountTime = value.longValue() * 1000L;
        // 广播时间是否在时间窗口范围内
        return System.currentTimeMillis() - accountTime <= CtrlConst.NOTIFY_ACCOUNT_EXIST_TIME * 1000L;
    }


    /**
     * 修正该房间并发连接数
     *
     * @param ilId               //房间ID
     * @param channelId          //房间ID对应频道ID
     * @param accountRemainCount //账户剩余并发连接数
     * @return int
     */
    public Integer adjustConnectCount(Integer accountRemainCount, Integer ilId, String channelId, String thirdId) {

        //锁状态控制房间并发连接更新频率
        String key = RedisKey.CTRL_REQ_CONNECT_LOCK + ilId;
        boolean lock = RedisLocker.tryLock(key, "1", CtrlConst.LOCK_TIME * 1000L);
        if (!lock) {
            return accountRemainCount;
        }

        //请求paas房间连接数
        Integer count = paasService.getConnectionCount(channelId);
        //房间下当前连接数数量
        String ckey = RedisKey.CONNECT_COUNT_BY_ROOM + ilId;
        Integer cvalue = (Integer) redisTemplate.opsForValue().get(ckey);
        int countCache = cvalue == null ? 0 : cvalue;
        log.info("并发控制-开始-参数-ilId={},进入者id={}，pass获取到的该房间所属channel的连接数={}", ilId, thirdId, count);
        log.info("并发控制-开始-参数-ilId={},进入者id={}，redis中房间下当前连接数数量={}", ilId, thirdId, cvalue);
        // 房间维持数量比
        Integer diffCount = countCache - count;
        log.info("connect_ctrl,修正该房间并发连接数,ilId={},connectCount={},cacheCount={},accountRemain={},diffCount={}",
                ilId, count, countCache, accountRemainCount, diffCount);
        // 应用和房间连接数进行更新
        if (diffCount >= 0) {
            accountRemainCount += diffCount;
            if (diffCount > 0) {
                redisTemplate.opsForValue().decrement(RedisKey.CONNECT_COUNT_BY_ROOM + ilId, diffCount.longValue());
                // redisTemplate.opsForValue().decrement(RedisKey.CONNECT_COUNT_BY_APP, diffCount);
            }
        } else {
            Integer absDiffCount = Math.abs(diffCount);
            //这个代码得意思 是 剩下得 和 差值 进行比较  如果剩下得 比差值大  则 相减
            //比如   比如  redis里面是 10   pass是  14
            //  数据库设置的是 15
            //  那么 剩下得  accountRemainCount=15-10 等于5个     redis-pass 等于 -4
            //   5比  -4得绝对值大  那么剩下 1个   其实就相当于  用 数据库设置得15 减去了 pass 得 14 等于1

            //比如  redis里面是 10   pass是  30

            //数据库设置的是 15
            //那肯定剩下为0  因为 pass 减去 redis得差值 是20  大于 数据库得 15了  所以肯定是 0

            accountRemainCount = accountRemainCount > absDiffCount ? accountRemainCount - absDiffCount : 0;
            redisTemplate.opsForValue().increment(RedisKey.CONNECT_COUNT_BY_ROOM + ilId, absDiffCount);
            //redisTemplate.opsForValue().increment(RedisKey.CONNECT_COUNT_BY_APP, absDiffCount);
        }
        return accountRemainCount;
    }

/*
    public Map<String, Integer> getConnectNum(Integer accountId) {
        Map<String, Integer> result = new HashMap<>();
        AnchorExtendsEntityDTO info = findByAccountId(accountId);
        result.put("connect_num", info == null ? 0 : info.getConnectNum());
        result.put("app_connect_num", maxConnectCount);
        return result;
    }*/

    /**
     * 获取系统的当前连接信息
     *
     * @return
     */
    @Override
    public JSONObject getSysConnectInfo() {
        final JSONObject jsonObject = new JSONObject();
        jsonObject.put("appMaxConnect", appMax);
        final Integer appTotalcount = getAppTotalcount();
        jsonObject.put("appConnect", appTotalcount);
        Integer appRemain = appMax - appTotalcount;
        jsonObject.put("appRadio", new BigDecimal(appTotalcount).divide(BigDecimal.valueOf(appMax), 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100L)) + "%");
        final JSONArray jsonArray = new JSONArray();

        Set<Integer> accountIds = redisTemplate.opsForSet().members(RedisKey.LIVING_ROOMS_OF_APPS);
        if (CollectionUtils.isNotEmpty(accountIds)) {
            for (Integer accountId : accountIds) {
                final JSONObject accountInfo = new JSONObject();
                Set<Integer> ilIds = redisTemplate.opsForSet().members(RedisKey.LIVING_ROOMS_OF_ACCOUNT + accountId);
                if (CollectionUtils.isEmpty(ilIds)) {
                    continue;
                }
                final JSONArray jsonArray1 = new JSONArray();
                Integer accountConnectTotal = 0;
                for (Integer ilid : ilIds) {
                    String k = RedisKey.CONNECT_COUNT_BY_ROOM + ilid;
                    Object v = redisTemplate.opsForValue().get(k);
                    final JSONObject jsonObject1 = new JSONObject();
                    Integer roomConnectTotal = v == null ? 0 : (Integer) v;
                    jsonObject1.put("il_Id", ilid);
                    jsonObject1.put("redis_cache_num", roomConnectTotal);
                    final RoomsEntityDTO roomInfoByIlId = roomInnerService.getRoomInfoByIlId(ilid.toString());
                    try {
                        jsonObject1.put("pass_num", paasService.getConnectionCount(roomInfoByIlId.getChannelId()));
                    } catch (Exception e) {
                        continue;
                    }
                    jsonArray1.add(jsonObject1);
                    accountConnectTotal += roomConnectTotal;
                }
                accountInfo.put("accountId", accountId);
                //anchor_extends  查询 用户 是否 设置 并发 控制 ，如果用户没有设置并发控制，直接返回
                AnchorExtendsEntityDTO info = findByAccountId(accountId);
                //如果系统中没有设置此主持人的最高并发，则取默认的最大值
                Integer accountMaxConnect = info != null && info.getConnectNum() > 0 ? info.getConnectNum() : accountMax;
                //如果没有账户默认的最大并发
//                Integer accountMaxConnect = info != null && info.getConnectNum()>0 ? info.getConnectNum() : appMax;
                accountInfo.put("accountMaxConnect", accountMaxConnect);
                accountInfo.put("accountRemainCount", accountMaxConnect - accountConnectTotal);
                BigDecimal divide = new BigDecimal(accountConnectTotal).divide(new BigDecimal(accountMaxConnect), 2, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
                accountInfo.put("accountRadio", divide + "%");
                accountInfo.put("rooms_connect", jsonArray1);
                jsonArray.add(accountInfo);
            }
        }
        jsonObject.put("appRemainConnect", appRemain);
        jsonObject.put("details", jsonArray);
        return jsonObject;
    }
}
